﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using System.Net;
using ThreeByte.Security;
using ThreeByte.ActiveDeck.Data;
using log4net;

namespace ThreeByte.ActiveDeck.Service
{
    public static class AuthorizationUtil
    {
        private static readonly XNamespace ns = ServiceConfiguration.NAMESPACE;
        private static readonly ILog log = LogManager.GetLogger(typeof(AuthorizationUtil));

        //Support transmitting non-encrypted passwords when we know we are using a secure channel
        public static XElement ToAuthenticationXML(NetworkCredential cred, bool plaintext = false) {
            XElement authNode = new XElement(ns + "Authentication");
            authNode.Add(new XAttribute("UserName", cred.UserName ?? string.Empty));
            if (plaintext) {
                authNode.Add(new XAttribute("Password", cred.Password ?? string.Empty));
                authNode.Add(new XAttribute("Plaintext", plaintext));
            } else {
                authNode.Add(new XAttribute("Password", Encryptor.Default.Encrypt(cred.Password ?? string.Empty)));
            }
            return authNode;
        }

        public static readonly NetworkCredential ANONYMOUS = new NetworkCredential("Anonymous", "Anonymous");
        public static NetworkCredential FromAuthenticationXML(XElement node) {
            string username = node.Attribute("UserName").Value;
            string password = node.Attribute("Password").Value;
            bool plaintext = false;  //By default all passwords are serialized in encrypted format
            if (node.Attribute("Plaintext") != null) {
                plaintext = bool.Parse(node.Attribute("Plaintext").Value);
            }
            if (username == string.Empty && password == string.Empty) {
                return ANONYMOUS;
            }
            if (!plaintext) {
                password = Encryptor.Default.Decrypt(password);
            }
            return new NetworkCredential(username, password);
        }

        public static bool IsAnonymousCredential(NetworkCredential cred) {
            return (cred.UserName == ANONYMOUS.UserName
                    && cred.Password == ANONYMOUS.Password
                    && cred.Domain == ANONYMOUS.Domain);
        }

        public static bool IsAnonymousUser(string username) {
            return (username == ANONYMOUS.UserName);
        }

        public static bool IsAuthorizedPresenter(string presentationId, string username) {

            //Do all username comparisons in lower case
            username = username.ToLower();

            ActiveDeckDataContext db = DataUtil.GetDataContext();
            Presenter presenter = (from p in db.Presenters
                                   where p.PresentationID == presentationId
                                    && p.UserName == username
                                   select p).SingleOrDefault();

            return (presenter != null);
        }

        public static bool IsAuthorizedParticipant(string presentation, string userName, bool makeRequest = false) {
            ActiveDeckDataContext db = DataUtil.GetDataContext();

            UserAccess access = (UserAccess)((from p in db.Presentations
                                  where p.ID == presentation
                                  select p.UserAccess).Single());

            switch (access) {
                case UserAccess.Approved: {
                        return IsUserApprovedForPresentation(presentation, userName, makeRequest);
                    }
                case UserAccess.Registered: {
                        return !IsAnonymousUser(userName);
                    }
                case UserAccess.Anonymous: {
                        return true;  //Anyone can access
                    }
            }
            throw new InvalidOperationException("Invalid UserAccess option");
        }

        public static bool IsUserBlockedForPresentation(string presentation, string userName) {

            //Do all username comparisons in lower case
            userName = userName.ToLower();

            ActiveDeckDataContext db = DataUtil.GetDataContext();

            //TODO: Benchmark this
            db.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, db.ApprovedUsers);

            ApprovedUser blockedUser = (from u in db.ApprovedUsers
                                         where u.PresentationID == presentation
                                         && u.UserName == userName
                                         && u.Blocked == true
                                         select u).SingleOrDefault();

            return (blockedUser != null);

        }

        public static bool IsUserApprovedForPresentation(string presentation, string userName, bool makeRequest = false) {

            //Do all username comparisons in lower case
            userName = userName.ToLower();

            ActiveDeckDataContext db = DataUtil.GetDataContext();

            //TODO: Benchmark this
            db.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, db.ApprovedUsers);

            ApprovedUser approvedUser = (from u in db.ApprovedUsers
                                     where u.PresentationID == presentation
                                     && u.UserName == userName
                                     select u).SingleOrDefault();

            if(approvedUser == null) {
                //See if this is the presenter asking for access
                bool isPresenter = IsAuthorizedPresenter(presentation, userName);

                //Will return true if this was the presenter - make request if asked
                if(makeRequest) {
                    if(isPresenter) {
                        log.InfoFormat("[{0}] Presenter auto-approved for access for user: {1}", presentation, userName);
                    } else {
                        log.InfoFormat("[{0}] Requesting access for user: {1}", presentation, userName);
                    }
                    approvedUser = new ApprovedUser();
                    approvedUser.PresentationID = presentation;
                    approvedUser.UserName = userName;
                    approvedUser.Approved = isPresenter;
                    approvedUser.Requested = true;
                    approvedUser.RequestedAt = DateTime.Now;

                    db.ApprovedUsers.InsertOnSubmit(approvedUser);
                    db.SubmitChanges();
                }

                return isPresenter;
            } else {
                if(approvedUser.Blocked ?? false) {
                    return false;
                }
                if(approvedUser.Approved) {
                    return true; //The only true return statement
                }
            }

            //Otherwise, the user has not been approved yet
            return false;
        }

        public static void ApproveUserForPresentation(string presentation, string userName, string approvedBy) {

            //Do all username comparisons in lower case
            userName = userName.ToLower();

            if(!IsAuthorizedPresenter(presentation, approvedBy)) {
                throw new InvalidOperationException("Presenter is not authorized to approve for this presentation");
            }

            ActiveDeckDataContext db = DataUtil.GetDataContext();

            //TODO: Benchmark this
            db.Refresh(System.Data.Linq.RefreshMode.OverwriteCurrentValues, db.ApprovedUsers);

            ApprovedUser approvedUser = (from u in db.ApprovedUsers
                                         where u.PresentationID == presentation
                                         && u.UserName == userName
                                         select u).SingleOrDefault();

            if(approvedUser != null) {
                //Update with approval
                log.InfoFormat("[{0}] Approving user request: {1}", presentation, userName);
                approvedUser.Approved = true;
                approvedUser.ApprovedAt = DateTime.Now;
                approvedUser.ApprovedBy = approvedBy;

            } else {
                //Create new entry
                log.InfoFormat("[{0}] Creating new approval for user: {1}", presentation, userName);
                approvedUser = new ApprovedUser();
                approvedUser.PresentationID = presentation;
                approvedUser.UserName = userName;
                approvedUser.Approved = true;
                approvedUser.ApprovedAt = DateTime.Now;
                approvedUser.ApprovedBy = approvedBy;

                db.ApprovedUsers.InsertOnSubmit(approvedUser);
            }

            db.SubmitChanges();

        }

        public static void RegisterParticipantDownload(string presentation, int version, string userName) {

            ActiveDeckDataContext db = DataUtil.GetDataContext();

            Participant p = new Participant();
            p.PresentationID = presentation;
            p.Version = version;
            p.UserName = userName.ToLower();
            p.DownloadedAt = DateTime.Now;

            db.Participants.InsertOnSubmit(p);
            db.SubmitChanges();

        }


    }
}
